using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using NFreeMarker.Cache;
using NFreeMarker.Core;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Template
{
    /**
     * Main entry point into the FreeMarker API, this class encapsulates the 
     * various configuration parameters with which FreeMarker is run, as well
     * as serves as a central template loading and caching point. Note that
     * this class uses a default strategy for loading 
     * and caching templates. You can plug in a replacement
     * template loading mechanism by using the {@link #setTemplateLoader(TemplateLoader)}
     * method.
     *
     * This object is <em>not synchronized</em>. Thus, the settings must not be changed
     * after you have started to access the object from multiple threads. If you use multiple
     * threads, set everything directly after you have instantiated the <code>Configuration</code>
     * object, and don't change the settings anymore.
     *
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     * @author Attila Szegedi
     * @version $Id: Configuration.java,v 1.122.2.5 2006/04/26 21:25:19 ddekany Exp $
     */

    public class Configuration : Configurable //, Cloneable
    {
        public static readonly string DefaultEncodingKey = "default_encoding";
        public static readonly string LocalizedLookupKey = "localized_lookup";
        public static readonly string StrictSyntaxKey = "strict_syntax";
        public static readonly string WhitespaceStrippingKey = "whitespace_stripping";
        public static readonly string CacheStorageKey = "cache_storage";
        public static readonly string TemplateUpdateDelayKey = "template_update_delay";
        public static readonly string AutoImportKey = "auto_import";
        public static readonly string AutoIncludeKey = "auto_include";
        public static readonly string TagSyntaxKey = "tag_syntax";

        public const int AutoDetectTagSyntax = 0;
        public const int AngleBracketTagSyntax = 1;
        public const int SquareBracketTagSyntax = 2;

        private static Configuration _defaultConfig = new Configuration();
        private static string _cachedVersion;
        private bool _strictSyntax = true;
        //private boolean localizedLookup = true;
        private bool _whitespaceStripping = true;
        private int _tagSyntax = AngleBracketTagSyntax;

        private TemplateCache _cache;
        private readonly Dictionary<string, object> _variables = new Dictionary<string, object>();
        private readonly Dictionary<string, Encoding> _encodingMap = new Dictionary<string, Encoding>();
        private readonly Dictionary<string, string> _autoImportMap = new Dictionary<string, string>();
        private readonly List<string> _autoImports = new List<string>();
        private readonly List<string> _autoIncludes = new List<string>();
        private Encoding _defaultEncoding = Encoding.Default;

        public Configuration()
        {
            _cache = new TemplateCache();
            _cache.SetConfiguration(this);
            _cache.SetDelay(5000);
            LoadBuiltInSharedVariables();
        }

        //public object clone() {
        //    try {
        //        Configuration copy = (Configuration)super.clone();
        //        copy.variables = new HashMap(variables);
        //        copy.encodingMap = new HashMap(encodingMap);
        //        copy.createTemplateCache(cache.getTemplateLoader(), cache.getCacheStorage());
        //        return copy;
        //    } catch (CloneNotSupportedException e) {
        //        throw new RuntimeException("Clone is not supported, but it should be: " + e.getMessage());
        //    }
        //}

        private void LoadBuiltInSharedVariables()
        {
            _variables.Add("capture_output", new CaptureOutput());
            _variables.Add("compress", StandardCompress.Instance);
            _variables.Add("html_escape", new HtmlEscape());
            _variables.Add("normalize_newlines", new NormalizeNewlines());
            _variables.Add("xml_escape", new XmlEscape());
        }

        /**
         * Loads a preset language-to-encoding map. It assumes the usual character
         * encodings for most languages.
         * The previous content of the encoding map will be lost.
         * This default map currently contains the following mappings:
         * <table>
         *   <tr><td>ar</td><td>ISO-8859-6</td></tr>
         *   <tr><td>be</td><td>ISO-8859-5</td></tr>
         *   <tr><td>bg</td><td>ISO-8859-5</td></tr>
         *   <tr><td>ca</td><td>ISO-8859-1</td></tr>
         *   <tr><td>cs</td><td>ISO-8859-2</td></tr>
         *   <tr><td>da</td><td>ISO-8859-1</td></tr>
         *   <tr><td>de</td><td>ISO-8859-1</td></tr>
         *   <tr><td>el</td><td>ISO-8859-7</td></tr>
         *   <tr><td>en</td><td>ISO-8859-1</td></tr>
         *   <tr><td>es</td><td>ISO-8859-1</td></tr>
         *   <tr><td>et</td><td>ISO-8859-1</td></tr>
         *   <tr><td>fi</td><td>ISO-8859-1</td></tr>
         *   <tr><td>fr</td><td>ISO-8859-1</td></tr>
         *   <tr><td>hr</td><td>ISO-8859-2</td></tr>
         *   <tr><td>hu</td><td>ISO-8859-2</td></tr>
         *   <tr><td>is</td><td>ISO-8859-1</td></tr>
         *   <tr><td>it</td><td>ISO-8859-1</td></tr>
         *   <tr><td>iw</td><td>ISO-8859-8</td></tr>
         *   <tr><td>ja</td><td>Shift_JIS</td></tr>
         *   <tr><td>ko</td><td>EUC-KR</td></tr>    
         *   <tr><td>lt</td><td>ISO-8859-2</td></tr>
         *   <tr><td>lv</td><td>ISO-8859-2</td></tr>
         *   <tr><td>mk</td><td>ISO-8859-5</td></tr>
         *   <tr><td>nl</td><td>ISO-8859-1</td></tr>
         *   <tr><td>no</td><td>ISO-8859-1</td></tr>
         *   <tr><td>pl</td><td>ISO-8859-2</td></tr>
         *   <tr><td>pt</td><td>ISO-8859-1</td></tr>
         *   <tr><td>ro</td><td>ISO-8859-2</td></tr>
         *   <tr><td>ru</td><td>ISO-8859-5</td></tr>
         *   <tr><td>sh</td><td>ISO-8859-5</td></tr>
         *   <tr><td>sk</td><td>ISO-8859-2</td></tr>
         *   <tr><td>sl</td><td>ISO-8859-2</td></tr>
         *   <tr><td>sq</td><td>ISO-8859-2</td></tr>
         *   <tr><td>sr</td><td>ISO-8859-5</td></tr>
         *   <tr><td>sv</td><td>ISO-8859-1</td></tr>
         *   <tr><td>tr</td><td>ISO-8859-9</td></tr>
         *   <tr><td>uk</td><td>ISO-8859-5</td></tr>
         *   <tr><td>zh</td><td>GB2312</td></tr>
         *   <tr><td>zh_TW</td><td>Big5</td></tr>
         * </table>
         * @see #clearEncodingMap
         * @see #setEncoding
         */

        public void LoadBuiltInEncodingMap()
        {
            _encodingMap.Clear();
            _encodingMap.Add("ar", Encoding.GetEncoding("ISO-8859-6"));
            _encodingMap.Add("be", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("bg", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("ca", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("cs", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("da", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("de", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("el", Encoding.GetEncoding("ISO-8859-7"));
            _encodingMap.Add("en", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("es", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("et", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("fi", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("fr", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("hr", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("hu", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("is", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("it", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("iw", Encoding.GetEncoding("ISO-8859-8"));
            _encodingMap.Add("ja", Encoding.GetEncoding("Shift_JIS"));
            _encodingMap.Add("ko", Encoding.GetEncoding("EUC-KR"));
            _encodingMap.Add("lt", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("lv", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("mk", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("nl", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("no", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("pl", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("pt", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("ro", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("ru", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("sh", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("sk", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("sl", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("sq", Encoding.GetEncoding("ISO-8859-2"));
            _encodingMap.Add("sr", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("sv", Encoding.GetEncoding("ISO-8859-1"));
            _encodingMap.Add("tr", Encoding.GetEncoding("ISO-8859-9"));
            _encodingMap.Add("uk", Encoding.GetEncoding("ISO-8859-5"));
            _encodingMap.Add("zh", Encoding.GetEncoding("GB2312"));
            _encodingMap.Add("zh_TW", Encoding.GetEncoding("Big5"));
        }

        /**
         * Clears language-to-encoding map.
         * @see #loadBuiltInEncodingMap
         * @see #setEncoding
         */

        public void ClearEncodingMap()
        {
            _encodingMap.Clear();
        }

        /**
         * Returns the default (singleton) Configuration object. Note that you can
         * create as many separate configurations as you wish; this global instance
         * is provided for convenience, or when you have no reason to use a separate
         * instance.
         * 
         * @deprecated The usage of the static singleton (the "default")
         * {@link Configuration} instance can easily cause erroneous, unpredictable
         * behavior. This is because multiple independent software components may use
         * FreeMarker internally inside the same application, so they will interfere
         * because of the common {@link Configuration} instance. Each such component
         * should use its own private {@link Configuration} object instead, that it
         * typically creates with <code>new Configuration()</code> when the component
         * is initialized.
         */

        public static Configuration GetDefaultConfiguration()
        {
            return _defaultConfig;
        }

        /**
         * Sets the Configuration object that will be retrieved from future calls
         * to {@link #getDefaultConfiguration()}.
         * 
         * @deprecated Using the "default" {@link Configuration} instance can
         * easily lead to erroneous, unpredictable behaviour.
         * See more {@link Configuration#getDefaultConfiguration() here...}.
         */

        public static void SetDefaultConfiguration(Configuration config)
        {
            _defaultConfig = config;
        }

        /**
         * Sets a template loader that is used to look up and load templates.
         * By providing your own template loader, you can customize the way
         * templates are loaded. Several convenience methods in this class already
         * allow you to install commonly used loaders:
         * {@link #setClassForTemplateLoading(Class, string)}, 
         * {@link #setDirectoryForTemplateLoading(File)}, and
         * {@link #setServletContextForTemplateLoading(object, string)}. By default,
         * a multi-loader is used that first tries to load a template from the file
         * in the current directory, then from a resource on the classpath.
         */
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetTemplateLoader(ITemplateLoader loader)
        {
            CreateTemplateCache(loader, _cache.GetCacheStorage());
        }

        private void CreateTemplateCache(ITemplateLoader loader, ICacheStorage storage)
        {
            TemplateCache oldCache = _cache;
            _cache = new TemplateCache(loader, storage);
            _cache.SetDelay(oldCache.GetDelay());
            _cache.SetConfiguration(this);
            // TOD: _cache.SetLocalizedLookup(_localizedLookup);
        }

        /**
         * @return the template loader that is used to look up and load templates.
         * @see #setTemplateLoader
         */
        public ITemplateLoader GetTemplateLoader()
        {
            return _cache.GetTemplateLoader();
        }

        //public synchronized void setCacheStorage(CacheStorage storage) {
        //    createTemplateCache(cache.getTemplateLoader(), storage);
        //}

        /// <summary>
        /// Set the explicit directory from which to load templates.
        /// </summary>
        /// <param name="dir">The directory that contains the templates.</param>
        public void SetDirectoryForTemplateLoading(DirectoryInfo dir)
        {
            ITemplateLoader tl = GetTemplateLoader();
            if (tl is FileTemplateLoader)
            {
                if (((FileTemplateLoader)tl).BaseDir.Equals(dir))
                {
                    return;
                }
            }
            SetTemplateLoader(new FileTemplateLoader(dir));
        }

        ///**
        // * Sets the servlet context from which to load templates
        // * @param sctxt the ServletContext object. Note that the type is <code>object</code>
        // *        to prevent class loading errors when user who uses FreeMarker not for
        // *        servlets has no javax.servlet in the CLASSPATH.
        // * @param path the path relative to the ServletContext.
        // * If this path is absolute, it is taken to be relative
        // * to the server's URL, i.e. http://myserver.com/
        // * and if the path is relative, it is taken to be 
        // * relative to the web app context, i.e.
        // * http://myserver.context.com/mywebappcontext/
        // */
        //public void setServletContextForTemplateLoading(object sctxt, string path) {
        //    try {
        //        if (path == null) {
        //            setTemplateLoader( (TemplateLoader)
        //                    ClassUtil.forName("freemarker.cache.WebappTemplateLoader")
        //                        .getConstructor(new Class[]{ClassUtil.forName("javax.servlet.ServletContext")})
        //                                .newInstance(new object[]{sctxt}) );
        //        }
        //        else {
        //            setTemplateLoader( (TemplateLoader)
        //                    ClassUtil.forName("freemarker.cache.WebappTemplateLoader")
        //                        .getConstructor(new Class[]{ClassUtil.forName("javax.servlet.ServletContext"), string.class})
        //                                .newInstance(new object[]{sctxt, path}) );
        //        }
        //    } catch (Exception exc) {
        //        throw new RuntimeException("Internal FreeMarker error: " + exc);
        //    }
        //}
        ///**
        // * Sets a class relative to which we do the 
        // * Class.getResource() call to load templates.
        // */
        //public void setClassForTemplateLoading(Class clazz, string pathPrefix) {
        //    setTemplateLoader(new ClassTemplateLoader(clazz, pathPrefix));
        //}
        ///**
        // * Set the time in seconds that must elapse before checking whether there is a newer
        // * version of a template file.
        // * This method is thread-safe and can be called while the engine works.
        // */
        //public void setTemplateUpdateDelay(int delay) {
        //    cache.setDelay(1000L * delay);
        //}
        ///**
        // * Sets whether directives such as if, else, etcetera
        // * must be written as #if, #else, etcetera.
        // * Any tag not starting with &lt;# or &lt;/# is considered as plain text
        // * and will go to the output as is. Tag starting with &lt# or &lt/# must
        // * be valid FTL tag, or else the template is invalid (i.e. &lt;#noSuchDirective>
        // * is an error).
        // */
        public void SetStrictSyntaxMode(bool b)
        {
            _strictSyntax = b;
        }

        /**
         * Tells whether directives such as if, else, etcetera
         * must be written as #if, #else, etcetera.
         *
         * @see #setStrictSyntaxMode
         */

        public bool GetStrictSyntaxMode()
        {
            return _strictSyntax;
        }

        /**
         * Sets whether the FTL parser will try to remove
         * superfluous white-space around certain FTL tags.
         */

        public void SetWhitespaceStripping(bool b)
        {
            _whitespaceStripping = b;
        }

        /**
         * Gets whether the FTL parser will try to remove
         * superfluous white-space around certain FTL tags.
         *
         * @see #setWhitespaceStripping
         */

        public bool GetWhitespaceStripping()
        {
            return _whitespaceStripping;
        }

        /**
         * Determines the syntax of the template files (angle bracket VS square bracket)
         * that has no <markup>ftl</markup> directive in it. The <code>tagSyntax</code>
         * parameter must be one of:
         * <ul>
         *   <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}:
         *     use the syntax of the first FreeMarker tag (can be anything, like <tt>list</tt>,
         *     <tt>include</tt>, user defined, ...etc)
         *   <li>{@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}:
         *     use the angle bracket syntax (the normal syntax)
         *   <li>{@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}:
         *     use the square bracket syntax
         * </ul>
         *
         * <p>In FreeMarker 2.3.x {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX} is the
         * default for better backward compatibility. Starting from 2.4.x {@link
         * Configuration#AUTO_DETECT_TAG_SYNTAX} is the default, so it is recommended to use
         * that even for 2.3.x.
         * 
         * <p>This setting is ignored for the templates that have <tt>ftl</tt> directive in
         * it. For those templates the syntax used for the <tt>ftl</tt> directive determines
         * the syntax.
         */

        public void SetTagSyntax(int tagSyntax)
        {
            if (tagSyntax != AutoDetectTagSyntax
                && tagSyntax != SquareBracketTagSyntax
                && tagSyntax != AngleBracketTagSyntax)
            {
                throw new ArgumentException(
                    "This can only be set to one of three settings: Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, or Configuration.SQAUARE_BRACKET_SYNTAX");
            }
            _tagSyntax = tagSyntax;
        }

        /**
         * @return whether the alternative square bracket
         * syntax is set as the default
         */

        public int GetTagSyntax()
        {
            return _tagSyntax;
        }

        /**
         * Equivalent to <tt>getTemplate(name, thisCfg.getLocale(), thisCfg.getEncoding(thisCfg.getLocale()), true)</tt>.
         */
        public Template GetTemplate(string name)
        {
            CultureInfo loc = GetLocale();
            return GetTemplate(name, loc, GetEncoding(loc), true);
        }

        /**
         * Equivalent to <tt>getTemplate(name, locale, thisCfg.getEncoding(locale), true)</tt>.
         */
        public Template GetTemplate(string name, CultureInfo locale)
        {
            return GetTemplate(name, locale, GetEncoding(locale), true);
        }
        /**
         * Equivalent to <tt>getTemplate(name, thisCfg.getLocale(), encoding, true)</tt>.
         */
        public Template GetTemplate(string name, Encoding encoding)
        {
            return GetTemplate(name, GetLocale(), encoding, true);
        }
        /**
         * Equivalent to <tt>getTemplate(name, locale, encoding, true)</tt>.
         */
        public Template GetTemplate(string name, CultureInfo locale, Encoding encoding)
        {
            return GetTemplate(name, locale, encoding, true);
        }

        /**
         * Retrieves a template specified by a name and locale, interpreted using
         * the specified character encoding, either parsed or unparsed. For the
         * exact semantics of parameters, see 
         * {@link TemplateCache#getTemplate(string, Locale, string, boolean)}.
         * @return the requested template.
         * @throws FileNotFoundException if the template could not be found.
         * @throws IOException if there was a problem loading the template.
         * @throws ParseException (extends <code>IOException</code>) if the template is syntactically bad.
         */
        public Template GetTemplate(string name, CultureInfo locale, Encoding encoding, bool parse)
        {
            Template result = _cache.GetTemplate(name, locale, encoding, parse);
            if (result == null) {
                throw new FileNotFoundException("Template " + name + " not found.");
            }
            return result;
        }

        /**
         * Sets the default encoding for converting bytes to characters when
         * reading template files in a locale for which no explicit encoding
         * was specified. Defaults to default system encoding.
         */
        public void SetDefaultEncoding(Encoding encoding)
        {
            _defaultEncoding = encoding;
        }

        /**
         * Gets the default encoding for converting bytes to characters when
         * reading template files in a locale for which no explicit encoding
         * was specified. Defaults to default system encoding.
         */
        public Encoding GetDefaultEncoding()
        {
            return _defaultEncoding;
        }

        /**
         * Gets the preferred character encoding for the given locale, or the 
         * default encoding if no encoding is set explicitly for the specified
         * locale. You can associate encodings with locales using 
         * {@link #setEncoding(Locale, string)} or {@link #loadBuiltInEncodingMap()}.
         * @param loc the locale
         * @return the preferred character encoding for the locale.
         */
        public Encoding GetEncoding(CultureInfo loc)
        {
            // Try for a full name match (may include country and variant)
            Encoding charset;
            if (!_encodingMap.TryGetValue(loc.Name, out charset))
            {
                CultureInfo parentLoc = loc.Parent;
                while (parentLoc != CultureInfo.InvariantCulture)
                {
                    if (_encodingMap.TryGetValue(parentLoc.Name, out charset))
                    {
                        _encodingMap.Add(loc.Name, charset);
                        break;
                    }
                    parentLoc = parentLoc.Parent;
                }
            }
            return charset ?? _defaultEncoding;
        }

        /**
         * Sets the character set encoding to use for templates of
         * a given locale. If there is no explicit encoding set for some
         * locale, then the default encoding will be used, what you can
         * set with {@link #setDefaultEncoding}.
         *
         * @see #clearEncodingMap
         * @see #loadBuiltInEncodingMap
         */
        public void SetEncoding(CultureInfo locale, Encoding encoding)
        {
            _encodingMap[locale.Name] = encoding;
        }

        /**
         * Adds a shared variable to the configuration.
         * Shared variables are variables that are visible
         * as top-level variables for all templates which use this
         * configuration, if the data model does not contain a
         * variable with the same name.
         *
         * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared variables,
         * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
         *
         * @param name the name used to access the data object from your template.
         *     If a shared variable with this name already exists, it will replace
         *     that.
         * @see #setSharedVariable(string,object)
         * @see #setAllSharedVariables
         */
        public void SetSharedVariable(string name, ITemplateModel tm)
        {
            _variables.Add(name, tm);
        }

        /**
         * Returns the set containing the names of all defined shared variables.
         * The method returns a new Set object on each call that is completely
         * disconnected from the Configuration. That is, modifying the set will have
         * no effect on the Configuration object.
         */

        public HashSet<string> GetSharedVariableNames()
        {
            return new HashSet<string>(_variables.Keys);
        }

        /**
         * Adds shared variable to the configuration.
         * It uses {@link Configurable#getObjectWrapper()} to wrap the 
         * <code>obj</code>.
         * @see #setSharedVariable(string,TemplateModel)
         * @see #setAllSharedVariables
         */

        public void SetSharedVariable(string name, object obj)
        {
            SetSharedVariable(name, GetObjectWrapper().Wrap(obj));
        }

        /**
         * Adds all object in the hash as shared variable to the configuration.
         *
         * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared variables,
         * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
         *
         * @param hash a hash model whose objects will be copied to the
         * configuration with same names as they are given in the hash.
         * If a shared variable with these names already exist, it will be replaced
         * with those from the map.
         *
         * @see #setSharedVariable(string,object)
         * @see #setSharedVariable(string,TemplateModel)
         */

        public void SetAllSharedVariables(ITemplateHashModelEx hash)
        {
            ITemplateModelIterator keys = hash.Keys().Iterator();
            ITemplateModelIterator values = hash.Values().Iterator();
            while (keys.HasNext())
            {
                SetSharedVariable(((ITemplateScalarModel) keys.Next()).GetAsString(), values.Next());
            }
        }

        /**
         * Gets a shared variable. Shared variables are variables that are 
         * available to all templates. When a template is processed, and an identifier
         * is undefined in the data model, a shared variable object with the same identifier
         * is then looked up in the configuration. There are several predefined variables
         * that are always available through this method, see the FreeMarker manual
         * for a comprehensive list of them.
         *
         * @see #setSharedVariable(string,object)
         * @see #setSharedVariable(string,TemplateModel)
         * @see #setAllSharedVariables
         */

        public ITemplateModel GetSharedVariable(string name)
        {
            object variable;
            if (_variables.TryGetValue(name, out variable))
            {
                return variable as ITemplateModel; // TODO: Can _variables store ITemplateModel objects?
            }
            return null;
        }

        ///**
        // * Removes all shared variables, except the predefined ones (compress, html_escape, etc.).
        // */
        //public void clearSharedVariables() {
        //    variables.clear();
        //    loadBuiltInSharedVariables();
        //}
        ///**
        // * Removes all entries from the template cache, thus forcing reloading of templates
        // * on subsequent <code>getTemplate</code> calls.
        // * This method is thread-safe and can be called while the engine works.
        // */
        //public void clearTemplateCache() {
        //    cache.clear();
        //}
        ///**
        // * Returns if localized template lookup is enabled or not.
        // * This method is thread-safe and can be called while the engine works.
        // */
        //public boolean getLocalizedLookup() {
        //    return cache.getLocalizedLookup();
        //}
        ///**
        // * Enables/disables localized template lookup. Enabled by default.
        // * This method is thread-safe and can be called while the engine works.
        // */
        //public void setLocalizedLookup(boolean localizedLookup) {
        //    this.localizedLookup = localizedLookup;
        //    cache.setLocalizedLookup(localizedLookup);
        //}
        ///**
        // * Sets a setting by name and string value.
        // *
        // * In additional to the settings understood by
        // * {@link Configurable#setSetting the super method}, it understands these:
        // * <ul>
        // *   <li><code>"auto_import"</code>: Sets the list of auto-imports. Example of valid value:
        // *       <br><code>/lib/form.ftl as f, /lib/widget as w, "/lib/evil name.ftl" as odd</code>
        // *       See: {@link #setAutoImports}
        // *   <li><code>"auto_include"</code>: Sets the list of auto-includes. Example of valid value:
        // *       <br><code>/include/common.ftl, "/include/evil name.ftl"</code>
        // *       See: {@link #setAutoIncludes}
        // *   <li><code>"default_encoding"</code>: The name of the charset, such as <code>"UTF-8"</code>.
        // *       See: {@link #setDefaultEncoding}
        // *   <li><code>"localized_lookup"</code>:
        // *       <code>"true"</code>, <code>"false"</code>, <code>"yes"</code>, <code>"no"</code>,
        // *       <code>"t"</code>, <code>"f"</code>, <code>"y"</code>, <code>"n"</code>.
        // *       Case insensitive.
        // *      See: {@link #setLocalizedLookup}
        // *   <li><code>"strict_syntax"</code>: <code>"true"</code>, <code>"false"</code>, etc.
        // *       See: {@link #setStrictSyntaxMode}
        // *   <li><code>"whitespace_stripping"</code>: <code>"true"</code>, <code>"false"</code>, etc.
        // *       See: {@link #setWhitespaceStripping}
        // *   <li><code>"cache_storage"</code>: If the value contains dot, then it is
        // *       interpreted as class name, and the object will be created with
        // *       its parameterless constructor. If the value does not contain dot,
        // *       then a {@link freemarker.cache.MruCacheStorage} will be used with the
        // *       maximum strong and soft sizes specified with the setting value. Examples
        // *       of valid setting values:
        // *       <table border=1 cellpadding=4>
        // *         <tr><th>Setting value<th>max. strong size<th>max. soft size
        // *         <tr><td><code>"strong:50, soft:500"</code><td>50<td>500
        // *         <tr><td><code>"strong:100, soft"</code><td>100<td><code>Integer.MAX_VALUE</code>
        // *         <tr><td><code>"strong:100"</code><td>100<td>0
        // *         <tr><td><code>"soft:100"</code><td>0<td>100
        // *         <tr><td><code>"strong"</code><td><code>Integer.MAX_VALUE</code><td>0
        // *         <tr><td><code>"soft"</code><td>0<td><code>Integer.MAX_VALUE</code>
        // *       </table>
        // *       The value is not case sensitive. The order of <tt>soft</tt> and <tt>strong</tt>
        // *       entries is not significant.
        // *       See also: {@link #setCacheStorage}
        // *   <li><code>"template_update_delay"</code>: Valid positive integer, the
        // *       update delay measured in seconds.
        // *       See: {@link #setTemplateUpdateDelay}
        // *   <li><code>"tag_syntax"</code>: Must be one of:
        // *       <code>"auto_detect"</code>, <code>"angle_bracket"</code>,
        // *       <code>"square_bracket"</code>.
        // * </ul>
        // *
        // * @param key the name of the setting.
        // * @param value the string that describes the new value of the setting.
        // *
        // * @throws UnknownSettingException if the key is wrong.
        // * @throws TemplateException if the new value of the setting can't be set
        // *     for any other reasons.
        // */
        //public void setSetting(string key, string value) throws TemplateException {
        //    if ("TemplateUpdateInterval".equalsIgnoreCase(key)) {
        //        key = TEMPLATE_UPDATE_DELAY_KEY;
        //    } else if ("DefaultEncoding".equalsIgnoreCase(key)) {
        //        key = DEFAULT_ENCODING_KEY;
        //    }
        //    try {
        //        if (DEFAULT_ENCODING_KEY.equals(key)) {
        //            setDefaultEncoding(value);
        //        } else if (LOCALIZED_LOOKUP_KEY.equals(key)) {
        //            setLocalizedLookup(StringUtil.getYesNo(value));
        //        } else if (STRICT_SYNTAX_KEY.equals(key)) {
        //            setStrictSyntaxMode(StringUtil.getYesNo(value));
        //        } else if (WHITESPACE_STRIPPING_KEY.equals(key)) {
        //            setWhitespaceStripping(StringUtil.getYesNo(value));
        //        } else if (CACHE_STORAGE_KEY.equals(key)) {
        //            if (value.indexOf('.') == -1) {
        //                int strongSize = 0;
        //                int softSize = 0;
        //                Map map = StringUtil.parseNameValuePairList(
        //                        value, string.valueOf(Integer.MAX_VALUE));
        //                Iterator it = map.entrySet().iterator();
        //                while (it.hasNext()) {
        //                    Map.Entry ent = (Map.Entry) it.next();
        //                    string pname = (string) ent.getKey();
        //                    int pvalue;
        //                    try {
        //                        pvalue = Integer.parseInt((string) ent.getValue());
        //                    } catch (NumberFormatException e) {
        //                        throw invalidSettingValueException(key, value);
        //                    }
        //                    if ("soft".equalsIgnoreCase(pname)) {
        //                        softSize = pvalue;
        //                    } else if ("strong".equalsIgnoreCase(pname)) {
        //                        strongSize = pvalue;
        //                    } else {
        //                        throw invalidSettingValueException(key, value);
        //                    }
        //                }
        //                if (softSize == 0 && strongSize == 0) {
        //                    throw invalidSettingValueException(key, value);
        //                }
        //                setCacheStorage(new MruCacheStorage(strongSize, softSize));
        //            } else {
        //                setCacheStorage((CacheStorage) ClassUtil.forName(value)
        //                        .newInstance());
        //            }
        //        } else if (TEMPLATE_UPDATE_DELAY_KEY.equals(key)) {
        //            setTemplateUpdateDelay(Integer.parseInt(value));
        //        } else if (AUTO_INCLUDE_KEY.equals(key)) {
        //            setAutoIncludes(new SettingStringParser(value).parseAsList());
        //        } else if (AUTO_IMPORT_KEY.equals(key)) {
        //            setAutoImports(new SettingStringParser(value).parseAsImportList());
        //        } else if (TAG_SYNTAX_KEY.equals(key)) {
        //            if ("auto_detect".equals(value)) {
        //                setTagSyntax(AUTO_DETECT_TAG_SYNTAX);
        //            } else if ("angle_bracket".equals(value)) {
        //                setTagSyntax(ANGLE_BRACKET_TAG_SYNTAX);
        //            } else if ("square_bracket".equals(value)) {
        //                setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX);
        //            } else {
        //                throw invalidSettingValueException(key, value);
        //            }
        //        } else {
        //            super.setSetting(key, value);
        //        }
        //    } catch(Exception e) {
        //        throw new TemplateException(
        //                "Failed to set setting " + key + " to value " + value,
        //                e, getEnvironment());
        //    }
        //}
        /**
         * Add an auto-imported template.
         * The importing will happen at the top of any template that
         * is vended by this Configuration object.
         * @param namespace the name of the namespace into which the template is imported
         * @param template the name of the template
         */
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void AddAutoImport(string ns, string template)
        {
            _autoImports.Remove(ns);
            _autoImports.Add(ns);
            _autoImportMap[ns] = template;
        }

        /**
         * Remove an auto-imported template
         * @param namespace the name of the namespace into which the template was imported
         */

        public void RemoveAutoImport(string ns)
        {
            _autoImports.Remove(ns);
            _autoImportMap.Remove(ns);
        }

        ///**
        // * set a map of namespace names to templates for auto-importing 
        // * a set of templates. Note that all previous auto-imports are removed.
        // */
        //public synchronized void setAutoImports(Map map) {
        //    autoImports = new ArrayList(map.keySet());
        //    if (map is HashMap) {
        //        autoImportMap = (Map) ((HashMap) map).clone();
        //    } 
        //    else if (map is SortedMap) {
        //        autoImportMap = new TreeMap(map);             
        //    }
        //    else {
        //        autoImportMap = new HashMap(map);
        //    }
        //}
        //protected void doAutoImportsAndIncludes(Environment env)
        //throws TemplateException, IOException
        //{
        //    for (int i=0; i<autoImports.size(); i++) {
        //        string namespace = (string) autoImports.get(i);
        //        string templateName = (string) autoImportMap.get(namespace);
        //        env.importLib(templateName, namespace);
        //    }
        //    for (int i = 0; i < autoIncludes.size(); i++) {
        //        string templateName = (string) autoIncludes.get(i);
        //        Template template = getTemplate(templateName, env.getLocale());
        //        env.include(template);
        //    }
        //}
        /**
         * add a template to be automatically included at the top of any template that
         * is vended by this Configuration object.
         * @param templateName the lookup name of the template.
         */
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void AddAutoInclude(string templateName)
        {
            _autoIncludes.Remove(templateName);
            _autoIncludes.Add(templateName);
        }

        /**
         * set the list of automatically included templates.
         * Note that all previous auto-includes are removed.
         */

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetAutoIncludes(List<string> templateNames)
        {
            _autoIncludes.Clear();
            _autoIncludes.AddRange(templateNames);
        }

        /**
         * remove a template from the auto-include list.
         * @param templateName the lookup name of the template in question.
         */

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void RemoveAutoInclude(string templateName)
        {
            _autoIncludes.Remove(templateName);
        }

        /**
         * Returns FreeMarker version number string. 
         * Examples of possible return values:
         * <code>"2.2.5"</code>, <code>"2.3pre13"</code>,
         * <code>"2.3pre13mod"</code>, <code>"2.3rc1"</code>, <code>"2.3"</code>,
         * <code>"3.0"</code>.
         *
         * <p>Notes on FreeMarker version numbering rules:
         * <ul>
         *   <li>"pre" and "rc" (lowercase!) means "preview" and "release
         *       candidate" respectively. It is must be followed with a
         *       number (as "1" for the first release candidate).
         *   <li>The "mod" after the version number indicates that it's an
         *       unreleased modified version of the released version.
         *       After releases, the nighly builds are such releases. E.g.
         *       the nightly build after releasing "2.2.1" but before releasing
         *       "2.2.2" is "2.2.1mod".
         *   <li>The 2nd version number must be present, and maybe 0,
         *       as in "3.0".
         *   <li>The 3rd version number is never 0. E.g. the version
         *       number string for the first release of the 2.2 series
         *       is "2.2", and NOT "2.2.0". 
         *   <li>When only the 3rd version number increases
         *       (2.2 -> 2.2.1, 2.2.1 -> 2.2.2 etc.), 100% backward compatiblity
         *       with the previous version MUST be kept.
         *       This means that <tt>freemarker.jar</tt> can be replaced in an
         *       application without risk (as far as the application doesn't depend
         *       on the presence of a FreeMarker bug).
         *       Note that backward compatibility restrictions do not apply for
         *       preview releases.
         * </ul>
         */

        public static string GetVersionNumber()
        {
            if (_cachedVersion != null)
            {
                return _cachedVersion;
            }

            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            var v = executingAssembly
                .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)
                .FirstOrDefault() as AssemblyInformationalVersionAttribute;
            if (v == null || string.IsNullOrEmpty(v.InformationalVersion))
            {
                throw new ApplicationException("Version is missing.");
            }

            _cachedVersion = v.InformationalVersion;

            return _cachedVersion;
        }
    }
}